/*
 * Copyright 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.privacysandbox.tools.core.generator

import androidx.privacysandbox.tools.core.generator.SpecNames.iBinderClass
import androidx.privacysandbox.tools.core.generator.SpecNames.resumeMethod
import androidx.privacysandbox.tools.core.generator.SpecNames.resumeWithExceptionMethod
import androidx.privacysandbox.tools.core.generator.SpecNames.suspendCancellableCoroutineMethod
import androidx.privacysandbox.tools.core.model.Types
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.MemberName
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeSpec

/**
 * Used in client-side code generation to ensure that `SdkActivityLauncher`s are not repeatedly
 * repackaged into binders.
 */
class SdkActivityLauncherProxyGenerator(private val basePackageName: String) {
    companion object {
        const val proxyClassName = "SdkActivityLauncherProxy"
        const val converterClassName = "SdkActivityLauncherConverter"
        val iSdkActivityLauncher =
            ClassName("androidx.privacysandbox.activity.core", "ISdkActivityLauncher")
    }

    fun generate(): FileSpec {
        val classSpec =
            TypeSpec.classBuilder(proxyClassName).build {
                addSuperinterface(Types.sdkActivityLauncher.poetClassName())
                addModifiers(KModifier.PUBLIC)
                primaryConstructor(
                    listOf(
                        PropertySpec.builder("remote", iSdkActivityLauncher).build(),
                        PropertySpec.builder(
                                "launcherInfo",
                                SpecNames.bundleClass,
                            )
                            .build(),
                    ),
                )
                addFunction(launchSdkActivityFunSpec())
            }

        return FileSpec.builder(basePackageName, proxyClassName).build {
            addCommonSettings()
            addType(classSpec)
            addType(converterObjectSpec())
        }
    }

    private fun launchSdkActivityFunSpec() =
        FunSpec.builder("launchSdkActivity").build {
            val transactionCallbackName =
                ClassName(
                    "androidx.privacysandbox.activity.core",
                    "ISdkActivityLauncherCallback",
                    "Stub"
                )
            val tokenParameterName = "sdkActivityHandlerToken"

            addModifiers(KModifier.PUBLIC)
            addModifiers(KModifier.OVERRIDE)
            addModifiers(KModifier.SUSPEND)
            addParameter(tokenParameterName, iBinderClass)
            returns(Boolean::class)
            addCode {
                addControlFlow("return %M", suspendCancellableCoroutineMethod) {
                    addStatement("remote.launchSdkActivity(")
                    indent()
                    addStatement("%L,", tokenParameterName)
                    addControlFlow("object: %T()", transactionCallbackName) {
                        addControlFlow(
                            "override fun onLaunchAccepted(%L: %T?)",
                            tokenParameterName,
                            iBinderClass,
                        ) {
                            addStatement("it.%M(true)", resumeMethod)
                        }
                        addControlFlow(
                            "override fun onLaunchRejected(%L: %T?)",
                            tokenParameterName,
                            iBinderClass,
                        ) {
                            addStatement("it.%M(true)", resumeMethod)
                        }
                        addControlFlow("override fun onLaunchError(message: String?)") {
                            addStatement(
                                "it.%M(RuntimeException(message))",
                                resumeWithExceptionMethod
                            )
                        }
                    }
                    unindent()
                    addStatement(")")
                }
            }
        }

    private fun converterObjectSpec() =
        TypeSpec.objectBuilder("SdkActivityLauncherConverter").build {
            val proxyPoetClassName = ClassName(basePackageName, proxyClassName)

            addFunction(
                FunSpec.builder("getLocalOrProxyLauncher").build {
                    addParameter("launcherInfo", SpecNames.bundleClass)
                    returns(Types.sdkActivityLauncher.poetTypeName())
                    addStatement(
                        """val remote = launcherInfo.getBinder("sdkActivityLauncherBinderKey")"""
                    )
                    addStatement(
                        """requireNotNull(remote) { "Invalid SdkActivityLauncher info bundle." }"""
                    )
                    addStatement(
                        "val binder = %T.Stub.asInterface(remote)",
                        iSdkActivityLauncher,
                    )
                    addStatement("return SdkActivityLauncherProxy(binder, launcherInfo)")
                }
            )

            addFunction(
                FunSpec.builder("toBinder").build {
                    addParameter("launcher", Types.sdkActivityLauncher.poetClassName())
                    returns(SpecNames.bundleClass)
                    addCode {
                        addControlFlow("if (launcher is %T)", proxyPoetClassName) {
                            addStatement("return launcher.launcherInfo")
                        }
                    }
                    addStatement(
                        "return launcher.%M()",
                        MemberName("androidx.privacysandbox.activity.client", "toLauncherInfo")
                    )
                }
            )
        }
}
